const fs = require("fs");
const path = require("path");
const config = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "config.json"), "utf8"));
const { execSync } = require("child_process");
const { FFMPEG_PATH } = require("./utils/ffmpeg");
const { getFirstFileNameWithoutExt } = require("./utils/common");
const { mergeVideosOrderedByList, mergeAudioVideo } = require("./utils/videoProcessor");
const { processFirstFrame } = require("./create_video_with_ffmpeg");
const { generateConcatVideoCommand } = require("./utils/ffmpegCommands");
const encodeFormat = config.encodeFormat || "h264_nvenc";
const { createCanvas, loadImage } = require("canvas");
const perWordDuration = 2;
/**
 * 所有临时脚本都存储在此，最后一并删除
 */
const tempFiles = [];
// 设置临时工作空间为 config.temp对应的目录下的glenworld目录
const tempDir = path.join(config.temp, "glenworld");
// step 1: 从https://apps.glenworld.org/deployment/KTal8Q5HNNThIP06gS24/glenLearn/lesson/[x] 网站上下载资源，
//   从temp\glenworld\lesson_groups.json中读取数据，创建0~8个目录在tempDir下
for (let i = 1; i <= 8; i++) {
  fs.mkdirSync(path.join(tempDir, i.toString()), { recursive: true });
}
const wordsInfo = JSON.parse(fs.readFileSync(path.join(tempDir, "flashcards.json")), { encoding: "utf8" });
// 创建课程目录：根据temp\glenworld\lessons.json中的json数据的每一个对象的ID作为课程ID，基于其groupId，在响应的目录下创建课程目录
const lessons = JSON.parse(fs.readFileSync(path.join(tempDir, "lessons.json"), "utf8"));

async function downLoadLessonResources() {
  console.log("  开始下载课程资源...");
  for (const lessonID of Object.keys(lessons)) {
    const lesson = lessons[lessonID];
    console.log(`  处理课程：${lessonID}`);
    const words = lesson.parameters.content;
    if (!words) continue;
    if (!words.length) continue;
    if (lesson.parameters.classes) continue;
    const lessonDir = path.join(tempDir, lesson.groupId.toString(), lessonID.toString());
    fs.mkdirSync(lessonDir, { recursive: true });
    for (const word of words) {
      const wordDir = path.join(lessonDir, word);
      fs.mkdirSync(wordDir, { recursive: true });
      const wordInfo = wordsInfo[word];
      const wordAudio = wordInfo["audio"];
      const wordImages = wordInfo["images"];
      const wordAudioUrlBase = `https://assets.glenworld.org/content/audio/flashcards/`;
      const wordImageUrlBase = `https://assets.glenworld.org/content/images/flashcards/`;
      // 下载音频到wordDir
      const wordAudioPath = path.join(wordDir, wordAudio);
      try {
        const arrayBuffer = await fetch(wordAudioUrlBase + wordAudio).then(res => res.arrayBuffer());
        const buffer = Buffer.from(arrayBuffer);
        fs.writeFileSync(wordAudioPath, buffer);
      } catch (error) {
        console.error("  下载音频文件时出错:", error.message);
        process.exit(1);
      }
      // 下载图片到wordDir
      for (const wordImage of wordImages) {
        const wordImagePath = path.join(wordDir, wordImage);
        try {
          const arrayBuffer = await fetch(wordImageUrlBase + wordImage).then(res => res.arrayBuffer());
          const buffer = Buffer.from(arrayBuffer);
          fs.writeFileSync(wordImagePath, buffer);
        } catch (error) {
          console.error("  下载图片文件时出错:", error.message);
          process.exit(1);
        }
      }
    }
  }
  // 把tempDir/glenworld目录下的全部空目录（递归）删除
  function deleteEmptyDirectories(dir) {
    const files = fs.readdirSync(dir);
    for (const file of files) {
      const filePath = path.join(dir, file);
      const stats = fs.statSync(filePath);
      if (stats.isDirectory()) {
        deleteEmptyDirectories(filePath);
        if (fs.readdirSync(filePath).length === 0) {
          fs.rmdirSync(filePath);
        }
      }
    }
  }
  deleteEmptyDirectories(tempDir);
}
// 下载describe资源
async function downLoadCommonResources() {
  console.log("  开始下载公共资源...");
  const describeResources = JSON.parse(fs.readFileSync(path.join(tempDir, "describe_it.json"), "utf8"));
  for (const resourceID of Object.keys(describeResources)) {
    const resource = describeResources[resourceID];
    console.log(`  处理资源：${resourceID}`);
    const resourceDir = path.join(tempDir, "describe_it", resourceID.toString());
    fs.mkdirSync(resourceDir, { recursive: true });
    const resourceAudio = resource.audio;
    console.log(`  resourceAudio ： ${resourceAudio}`);
    const resourceAudioUrlBase = `https://assets.glenworld.org/content/audio/`;
    const resourceAudioPath = path.join(resourceDir, resourceAudio);
    console.log(`  resourceAudioPath : ${resourceAudioPath}`);
    let pDir = path.dirname(resourceAudioPath);
    console.log(`  pDir : ${pDir}`);
    fs.mkdirSync(pDir, { recursive: true });
    try {
      const arrayBuffer = await fetch(resourceAudioUrlBase + resourceAudio).then(res => res.arrayBuffer());
      const buffer = Buffer.from(arrayBuffer);
      fs.writeFileSync(resourceAudioPath, buffer);
    } catch (error) {
      console.error("  下载音频文件时出错:", error.message);
      process.exit(1);
    }
    const resourceImage = resource.image;
    const resourceImageUrlBase = `https://assets.glenworld.org/content/images/`;
    const resourceImagePath = path.join(resourceDir, resourceImage);
    pDir = path.dirname(resourceImagePath);
    fs.mkdirSync(pDir, { recursive: true });
    try {
      const arrayBuffer = await fetch(resourceImageUrlBase + resourceImage + "_medium.png").then(res => res.arrayBuffer());
      const buffer = Buffer.from(arrayBuffer);
      fs.writeFileSync(resourceImagePath + "_medium.png", buffer);
    } catch (error) {
      console.error("  下载图片文件时出错:", error.message);
      process.exit(1);
    }
    const structure = resource.structure;
    for (var i = 0; i < structure.length; i++) {
      const structureItem = structure[i];
      const answer = structureItem.answer;
      const audio = structureItem.audio;
      if (!audio) continue;
      console.log(`  answer ： ${answer}`);
      const audioUrlBase = `https://assets.glenworld.org/content/audio/`;
      console.log("  == > ");
      console.log(resourceDir, answer, audio);
      const audioPath = path.join(resourceDir, answer, audio);
      console.log(`  audio ： ${audio}`);
      pDir = path.dirname(audioPath);
      console.log(`  audioPath ： ${audioPath}`);
      fs.mkdirSync(pDir, { recursive: true });
      try {
        const arrayBuffer = await fetch(audioUrlBase + audio).then(res => res.arrayBuffer());
        const buffer = Buffer.from(arrayBuffer);
        fs.writeFileSync(audioPath, buffer);
      } catch (error) {
        console.error("  下载音频文件时出错:", error.message);
        process.exit(1);
      }
    }
  }
}

// setp 2: 获取课程信息，并保存到临时工作空间下的glenworld/x/info.txt文件
// 直接在lessons.json 的每一个条目的introAudio（数组）对象的最后一条作为课程介绍
function getLessonInfo(pLessonID) {
  for (const lessonID of Object.keys(lessons)) {
    console.log(` 正在处理课程 ${lessonID}...,pLessonID:${pLessonID}`);
    if (lessonID === pLessonID) {
      return lessons[lessonID];
    }
  }
  return null;
}

function getLessonName(pLessonID) {
  const lessonInfo = getLessonInfo(pLessonID);
  if (lessonInfo) {
    const intro = lessonInfo.introAudio;
    if (!intro) return getWordsByLessonID(pLessonID).words.join(",");
    return intro[intro.length - 1];
  }
  return null;
}
/**
 * 根据lessonID 在tempDir/glenworld/下0-9/lessonID文件夹，查找其下的子文件夹名称就是每个单词
 * @param {Number} lessonID 课程编号
 */
function getWordsByLessonID(lessonID) {
  for (let i = 0; i < 10; i++) {
    const lessonDir = path.join(tempDir, i.toString(), lessonID.toString());
    console.log("  lessonDir:", lessonDir);
    if (fs.existsSync(lessonDir)) {
      // 获取words,读取lessionDir下的所有文件夹名称
      const words = fs.readdirSync(lessonDir).filter(file => {
        return fs.statSync(path.join(lessonDir, file)).isDirectory();
      });
      return { words, lessonDir };
    }
  }
  const words = [];
  let lessonDir = "";
  return { words, lessonDir };
}
function getWordImgPath(lessonDir, word) {
  const wordDir = path.join(lessonDir, word);
  // 查找wordDir目录下的png，返回第一个
  const wordImg = fs.readdirSync(wordDir).find(file => file.endsWith(".png"));
  return wordImg ? path.join(wordDir, wordImg) : null;
}

function getWordAudioPath(lessonDir, word) {
  const wordDir = path.join(lessonDir, word);
  // 获取wordDir目录下的mp3，返回第一个
  const wordAudio = fs.readdirSync(wordDir).find(file => file.endsWith(".mp3"));
  return wordAudio ? path.join(wordDir, wordAudio) : null;
}

async function convertImageToVideo(imagePath, videoPath, dur) {
  console.log(`  convertImageToVideo: ${imagePath}`);
  let configVideoWidth = config.projectSettings.videoWidth || 1920;
  let configVideoHeight = config.projectSettings.videoHeight || 1080;
  // 给imagePath加入背景paper
  const canvas = createCanvas(configVideoWidth, configVideoHeight);

  const ctx = canvas.getContext("2d");
  const paper = await loadImage(path.join(tempDir, "paper.jpeg"));
  ctx.drawImage(paper, 0, 0, configVideoWidth, configVideoHeight);
  const image = await loadImage(imagePath);
  const maxW = Math.max(image.width, configVideoWidth * 0.8);
  const maxH = Math.max(image.height, configVideoHeight * 0.3);
  const x = (configVideoWidth - maxW) / 2;
  const y = (configVideoHeight - maxH) / 2;
  ctx.drawImage(image, x, y,maxW, maxH);
  const imagePathDir = path.dirname(imagePath);
  const imageName = path.basename(imagePath);
  const wordImgWithBg = path.join(imagePathDir, `${imageName}_with_background.png`);
  fs.writeFileSync(wordImgWithBg, canvas.toBuffer("image/png"));
  tempFiles.push(wordImgWithBg);

  const duration = dur || perWordDuration;
  // 使用FFmpeg将图片转换为视频，保持正确的宽高比
  try {
    const convertCommand = `"${FFMPEG_PATH}" -y -loglevel quiet -loop 1 -i "${wordImgWithBg}" -vf "scale=w=${configVideoWidth}:h=${configVideoHeight}:force_original_aspect_ratio=decrease,crop=w=min(${configVideoWidth}\\,iw):h=min(${configVideoHeight}\\,ih):x=(iw-min(${configVideoWidth}\\,iw))/2:y=(ih-min(${configVideoHeight}\\,ih))/2,pad=w=${configVideoWidth}:h=${configVideoHeight}:x=(ow-iw)/2:y=(oh-ih)/2:color=black" -c:v ${encodeFormat} -t ${duration} -pix_fmt yuv420p -r 30 "${videoPath}"`;
    execSync(convertCommand);
  } catch (error) {
    console.error("  图片转换为视频时出错:", error.message);
    process.exit(1);
  }
  console.log("  视频已生成:", videoPath);
}

function addTextToVideo(text, videoPath, outputPath, style) {
  if (text.endsWith("_color")) text = text.replace("_color", "");
  if (!style) style = `:x=(w-text_w)/2:y=h*0.8:fontsize=128:fontcolor=white:box=1:boxcolor=red:boxborderw=10`;
  let command = `${FFMPEG_PATH} -y -loglevel error -i "${videoPath}" -vf "drawtext=fontfile='C\\:/Windows/Fonts/Dynapuff.ttc':text='${text}'${style}" "${outputPath}"`;
  if (text.endsWith(".txt")) {
    command = `${FFMPEG_PATH} -y -loglevel error -i "${videoPath}" -vf "drawtext=textfile='${text}':fontfile='C\\:/Windows/Fonts/Dynapuff.ttc'${style}" "${outputPath}"`;
  }
  console.log("  执行命令:", command);
  try {
    execSync(command);
  } catch (error) {
    console.error("  添加文字到视频时出错:", error.message);
    process.exit(1);
  }
}

/**
 * 智能文本换行
 * @param {string} str - 要处理的字符串
 * @param {Canvas} canvas - 画布对象
 * @param {CanvasRenderingContext2D} ctx - 画布上下文对象
 * @returns {string} 处理后的字符串
 */
function smartTextWrap(str, canvas, ctx) {
  // 计算宽度阈值（canvas宽度的80%）
  const widthThreshold = canvas.width * 0.8;
  ctx.font = `64px italic`;
  // 检查字符串宽度是否超过阈值
  const textWidth = ctx.measureText(str).width;

  // 如果未超过阈值，直接返回原字符串
  if (textWidth <= widthThreshold) {
    return str;
  }

  // 从中间位置开始寻找空格
  let middle = Math.floor(str.length / 2);
  let splitIndex = -1;

  // 先向字符串末尾方向查找空格
  for (let i = middle; i < str.length; i++) {
    if (str[i] === " ") {
      splitIndex = i;
      break;
    }
  }

  // 如果未找到，再向字符串开始方向查找空格
  if (splitIndex === -1) {
    for (let i = middle; i >= 0; i--) {
      if (str[i] === " ") {
        splitIndex = i;
        break;
      }
    }
  }

  // 如果找不到空格，直接在中间位置分割
  if (splitIndex === -1) {
    splitIndex = middle;
  }

  // 分割字符串为两部分
  const part1 = str.substring(0, splitIndex);
  const part2 = str.substring(splitIndex + 1); // +1 是为了跳过空格

  // 递归处理两部分，然后用换行符连接
  const wrappedPart1 = smartTextWrap(part1, canvas, ctx);
  const wrappedPart2 = smartTextWrap(part2, canvas, ctx);

  return `${wrappedPart1}\n${wrappedPart2}`;
}

function replaceMiddleSpace(str) {
  // 将首字母大写
  str = str.charAt(0).toUpperCase() + str.slice(1);
  console.log("  正在处理句子:", str);
  const canvas = createCanvas(config.projectSettings.videoWidth, config.projectSettings.videoHeight);
  const ctx = canvas.getContext("2d");
  return smartTextWrap(str, canvas, ctx);
}
// 更加单词获取对应的句子音频和图片以及字幕
// 在tempDir/describe_it/下找到名称为word的目录
// 如果找到，则返回tempDir/describe_it/describe_it/下的png图片和mp3音频,并且把音频的名称作为字幕字符串返回
function getImageAndAudio(word) {
  const describeItDir = path.join(`${tempDir}/describe_it`);
  // 查找describeItDir的下下级目录，如果匹配单词word名称，则返回与word并列的目录
  const describeItDirSubDir = fs.readdirSync(describeItDir, { recursive: false });
  console.log("  开始搜索单词资源...,在 ", describeItDir, "目录...");
  const dirsCoundUse = [];
  for (const dir of describeItDirSubDir) {
    // console.log("  正在搜索单词资源...", dir);
    const subDirs = fs.readdirSync(path.join(describeItDir, dir), { recursive: false });
    for (const subDir of subDirs) {
      // console.log("  正在搜索单词资源...", dir, "/", subDir, "subDir:", subDir);

      if (subDir === word) {
        const recourceDir = path.join(describeItDir, dir, "describe_it");
        dirsCoundUse.push(recourceDir);
      }
    }
  }
  if (dirsCoundUse.length > 0) return getImageAndAudioRandom(dirsCoundUse);
  else return { imagePath: null, audioPath: null, text: null };
}

function getImageAndAudioRandom(recourceDirs) {
  // 找到匹配单词的目录，返回该目录下的png图片和mp3音频
  const recourceDir = recourceDirs[Math.floor(Math.random() * recourceDirs.length)];
  console.log("  找到匹配单词的目录，返回该目录下的png图片和mp3音频...", recourceDir);
  const imagePath = getFirstFileNameWithoutExt(recourceDir, ".png") + ".png";
  console.log("    找到图片，imagePath:", imagePath);
  const audioPath = getFirstFileNameWithoutExt(recourceDir, ".mp3") + ".mp3";
  console.log("    找到音频，audioPath:", audioPath);
  // 将audioPath的下划线转化为空格，然后将首字母大写作为描述
  let text = audioPath.replace(".mp3", "").replace(/_/g, " ");
  text = text.charAt(0).toUpperCase() + text.slice(1);
  console.log("    将audioPath的下划线转化为空格，然后将首字母大写作为描述，text:", text);
  const iPath = path.join(recourceDir, imagePath);
  const aPath = path.join(recourceDir, audioPath);
  console.log("    iPath:", iPath);
  console.log("    aPath:", aPath);
  return { imagePath: iPath, audioPath: aPath, text: text };
}

/**
 * 根据音频文件路径获取音频长度
 * @param {String} audioPath 音频路径
 */
function getAudioDuration(audioPath) {
  try {
    // 处理路径，确保在Windows环境下正确识别
    const fullFilePath = path.resolve(audioPath);

    // 执行ffmpeg命令，注意转义路径中的空格
    const command = `"${FFMPEG_PATH}" -i "${fullFilePath}" 2>&1`;
    let output;

    try {
      // 尝试执行命令，忽略错误码（因为ffmpeg在仅查看信息时会返回非0码）
      output = execSync(command, {
        encoding: "utf8",
        stdio: ["pipe", "pipe", "pipe"], // 合并输出流
      });
    } catch (err) {
      // 捕获错误，但使用错误输出中的内容（因为ffmpeg信息在stderr）
      output = err.stdout + err.stderr;
    }

    // 正则表达式匹配时长信息
    const durationMatch = output.match(/Duration: (\d+:\d+:\d+\.\d+)/);
    if (!durationMatch) {
      throw new Error("无法提取时长信息: " + output.substring(0, 200));
    }

    const durationStr = durationMatch[1];
    const [hours, minutes, seconds] = durationStr.split(":").map(Number);

    // 转换为总秒数
    const totalSeconds = hours * 3600 + minutes * 60 + seconds;
    return Math.round(totalSeconds * 100) / 100;
  } catch (error) {
    console.error("获取时长失败:", error.message);
    process.exit(1);
  }
}

/**
 * 根据getImageAndAudio返回的对象创建视频
 * @param {String} lessonDir
 * @param {String} word
 * @param {Ojbect} resources
 * @param {String} outputFile
 */
async function createWordSentenceVideo(lessonDir, word, resources, outputFile) {
  // 通过ffmpeg，计算resources中音频的长度
  const audioDuration = getAudioDuration(resources.audioPath);
  // 将图片转换为长度为音频时长的视频
  const wordVideoNoAudioPath = path.join(lessonDir, `${word}_noVideo.mp4`);
  tempFiles.push(wordVideoNoAudioPath);
  await convertImageToVideo(resources.imagePath, wordVideoNoAudioPath, audioDuration);
  // 将音频与视频合并
  const wordVideoWithAudioPath = path.join(lessonDir, `${word}_withVideo.mp4`);
  tempFiles.push(wordVideoWithAudioPath);
  mergeAudioVideo(wordVideoNoAudioPath, resources.audioPath, wordVideoWithAudioPath, audioDuration);
  // 加入字幕（内容部分）
  const style = `:x=(w-text_w)/2:y=h*0.8:fontsize=64:fontcolor=yellow:box=1:boxcolor=red:boxborderw=10`;
  addTextToVideo(resources.text, wordVideoWithAudioPath, outputFile, style);
}

// setp 4: 创建视频开头
async function createVideoStart(lessonName, outputPath) {
  // 获取config.inputStartVoice对应的音频文件
  const startVoicePath = path.join(__dirname, "..", config.inputStartVoice);
  console.log(`  开始语音文件为：${startVoicePath}`);
  // 获取第一帧图片并添加文字
  console.log(`  处理首帧图片...`);
  // 处理首帧图片
  const outputDir = path.join(__dirname, "../output");
  console.log(`  输出目录为：${outputDir}`);
  let firstFrameWithTextPath = null;
  const customTitle = `Words for kids\n${lessonName}`;
  const titleFilePath = path.join(__dirname, "../temp/", "temp_title.txt");
  console.log(`  设置视频标题: ${customTitle}`);
  fs.writeFileSync(titleFilePath, customTitle, "utf-8");
  async function addTxt2FirstFrame() {
    console.log(`  1. 处理首帧图片...`);
    firstFrameWithTextPath = await processFirstFrame(outputDir);
    console.log(`  2. 处理完成，图片保存在：${firstFrameWithTextPath}`);
  }

  await addTxt2FirstFrame();

  console.log(`  处理完成，首帧图片保存在：${firstFrameWithTextPath}`);
  if (!firstFrameWithTextPath) {
    console.log(`  没有有效的首帧图片，请检查输入参数或处理过程。`);
    process.exit(1);
  }

  // 将第一帧图片转化为视频V1
  console.log("  Processing first frame...");

  const firstFrameOutputPath = path.join(tempDir, `img_first.mp4`);
  tempFiles.push(firstFrameOutputPath);
  // 从配置文件获取视频尺寸
  let configVideoWidth = 1920;
  let configVideoHeight = 1080;
  try {
    configVideoWidth = config.projectSettings.videoWidth || 1920;
    configVideoHeight = config.projectSettings.videoHeight || 1080;
  } catch (error) {
    console.warn("无法加载配置文件，使用默认尺寸:", error.message);
  }
  const firstFrameDuration = config.projectSettings.firstFrameDuration || 0.5;
  // 使用FFmpeg将图片转换为视频，保持正确的宽高比
  try {
    const convertCommand = `"${FFMPEG_PATH}" -y -loglevel quiet -loop 1 -i "${firstFrameWithTextPath}" -vf "scale=w=${configVideoWidth}:h=${configVideoHeight}:force_original_aspect_ratio=decrease,crop=w=min(${configVideoWidth}\\,iw):h=min(${configVideoHeight}\\,ih):x=(iw-min(${configVideoWidth}\\,iw))/2:y=(ih-min(${configVideoHeight}\\,ih))/2,pad=w=${configVideoWidth}:h=${configVideoHeight}:x=(ow-iw)/2:y=(oh-ih)/2:color=black" -c:v ${encodeFormat} -t ${firstFrameDuration} -pix_fmt yuv420p -r 30 "${firstFrameOutputPath}"`;
    execSync(convertCommand);
  } catch (error) {
    console.error("  图片转换为视频时出错:", error.message);
    process.exit(1);
  }

  // 将音频startVoicePath和V1合并 m1.mp4
  const mergeSuccess = mergeAudioVideo(firstFrameOutputPath, startVoicePath, outputPath, firstFrameDuration);
  if (mergeSuccess) {
    console.log("  合并开始语音文件到视频中成功");
  } else {
    console.warn("  合并开始语音文件到视频中失败");
    process.exit(1);
  }
}
// step 3: 创建视频内容部分
async function createContent(lessonID) {
  console.log("  开始处理课程...,", lessonID);
  // 根据lessonID 获取单词列表
  const { words, lessonDir } = getWordsByLessonID(lessonID);
  console.log("  words:", words);
  if (words.length === 0) return;
  console.log("  lessonDir:", lessonDir);
  // 遍历每一个单词实现：
  const wordFinalVideosPath = [];
  const listFile = path.join(lessonDir, "list.txt");
  console.log("  listFile:", listFile);
  let listContent = "";
  tempFiles.push(listFile);
  let inputDuration = 0.0;
  for (let i = 0; i < words.length; i++) {
    const word = words[i];
    console.log("  word:", word);
    // 获取单词的图片
    const imgPath = getWordImgPath(lessonDir, word);
    // 获取单词的音频
    const audioPath = getWordAudioPath(lessonDir, word);
    const videoPath = path.join(lessonDir, `${word}.mp4`);
    tempFiles.push(videoPath);
    // step 3.1 图片转视频，将图片转为长1秒的视频
    console.log(`  2. 图片转视频...,videoPath:${videoPath}`);
    await convertImageToVideo(imgPath, videoPath);
    // step 3.1.1 将视频加入单词声音，总时长为视频时长，保存为 temp/glenworld/lessonID/[word_a]_no_subtitle.mp4
    const videoPathNoSubtitle = path.join(lessonDir, `${word}_no_subtitle.mp4`).replace(/ /g, "_");
    tempFiles.push(videoPathNoSubtitle);
    console.log(`  3. 将视频加入单词声音...`);
    mergeAudioVideo(videoPath, audioPath, videoPathNoSubtitle, perWordDuration);
    // step 3.2 temp/glenworld/x/output_no_subtitle.mp4 加入文字，保存为 temp/glenworld/lessonID/[word_a]_with_subtitle.mp4
    const videoPathWithSubtitle = path.join(lessonDir, `${word}_with_subtitle.mp4`).replace(/ /g, "_");
    tempFiles.push(videoPathWithSubtitle);
    console.log(`  3.2 将视频加入文字...`);
    addTextToVideo(word, videoPathNoSubtitle, videoPathWithSubtitle);
    wordFinalVideosPath.push(videoPathNoSubtitle);
    wordFinalVideosPath.push(videoPathWithSubtitle);
    console.log(`  开始获取单词资源...`);

    const wordSentenceVideoPath = path.join(lessonDir, `${word}_sentence.mp4`);
    tempFiles.push(wordSentenceVideoPath);

    listContent += `file '${path.basename(videoPathNoSubtitle)}'\n`;
    inputDuration = getAudioDuration(videoPathNoSubtitle);
    listContent += `duration ${inputDuration}\n`;
    listContent += `file '${path.basename(videoPathWithSubtitle)}'\n`;
    inputDuration = getAudioDuration(videoPathWithSubtitle);
    listContent += `duration ${inputDuration}\n`;
    const res = getImageAndAudio(word);
    if (res.audioPath) {
      await createWordSentenceVideo(lessonDir, word, res, wordSentenceVideoPath);
      listContent += `file '${path.basename(wordSentenceVideoPath)}'\n`;
      inputDuration = getAudioDuration(wordSentenceVideoPath);
      listContent += `duration ${inputDuration}\n`;
    }
  }
  const finallVideoPath = path.join(lessonDir, "finall.mp4");
  tempFiles.push(finallVideoPath);
  console.log(`  3.3 将视频合并...`);
  fs.writeFileSync(listFile, listContent, { encoding: "utf-8" });
  const outputMergeContentPath = path.join(lessonDir, `${lessonID}_MergeContent.mp4`);
  tempFiles.push(outputMergeContentPath);
  tempFiles.push(listFile);

  // 使用更适合处理统一视频格式的参数  
  process.env.EFFECT = "smoothleft"; //wipetl
  let videoCommand = generateConcatVideoCommand(listFile.replace(/\\/g, "/"), outputMergeContentPath, true);
  process.env.EFFECT = undefined;
  // console.log('\n  执行视频生成命令:', videoCommand);
  console.log("\n  执行视频生成命令...");

  // 将命令写入临时脚本文件执行，避免命令行太长问题
  const tempDir = path.dirname(listFile.replace(/\\/g, "/"));
  const scriptPath = path.join(tempDir, "concat_script.bat");
  fs.writeFileSync(scriptPath, videoCommand, "utf-8");
  execSync(`"${scriptPath}"`, { stdio: "ignore" });
  // 执行完后删除临时脚本文件
  if (fs.existsSync(scriptPath)) {
    fs.unlinkSync(scriptPath);
  }

  // mergeVideosOrderedByList(listFile.replace(/\\/g, "/"), outputMergeContentPath, true);
  const videoContentWithTitlePath = path.join(lessonDir, `${lessonID}_ContentwithTitle.mp4`);
  tempFiles.push(videoContentWithTitlePath);
  const style = `:x=(w-text_w)/2:y=h*0.1:fontsize=64:fontcolor=orange`;
  let lessonName = getLessonName(lessonID);
  if (!lessonName) lessonName = "";
  lessonName = lessonName.replace(/\_/g, " ");
  lessonName = lessonName.charAt(0).toUpperCase() + lessonName.slice(1);
  const lessonNameOrginal = lessonName + "";
  // 将lessonName进行换行处理，如果lessonName的空格数超过三个，则将最中间的一个空格替换为换行符
  lessonName = replaceMiddleSpace(lessonName);

  let lessonNameTempFile = path.join(lessonDir, "lessonName.txt");
  lessonNameTempFile = lessonNameTempFile.replace(/\\/g, "/");
  tempFiles.push(lessonNameTempFile);
  fs.writeFileSync(lessonNameTempFile, lessonName, "utf-8");
  addTextToVideo(lessonNameTempFile, outputMergeContentPath, videoContentWithTitlePath, style);
  const startVideoPath = path.join(lessonDir, "start.mp4");
  tempFiles.push(startVideoPath);
  await createVideoStart(lessonNameOrginal, startVideoPath);
  // setp 5: 合并视频内容和开头
  const outputPath = path.join(lessonDir, `output.mp4`);
  const listFileFinalPath = path.join(lessonDir, "list_Final.txt");
  tempFiles.push(listFileFinalPath);
  const listFileFinalContent = `file ${path.basename(startVideoPath)}\nfile ${path.basename(videoContentWithTitlePath)}\n`;
  fs.writeFileSync(listFileFinalPath, listFileFinalContent, "utf-8");
  process.env.EFFECT = "smoothleft"; //wipetl
  mergeVideosOrderedByList(listFileFinalPath.replace(/\\/g, "/"), outputPath);
  process.env.EFFECT = undefined;
}

async function main() {
  console.log("  开始处理...");
  // downLoadLessonResources().then(() => {
  //   console.log("下载完成");
  //   process.exit(0);
  // });
  //   downLoadCommonResources().then(() => {
  //     console.log("下载完成");
  //     process.exit(0);
  //   });
  // for (const lessonID of Object.keys(lessons)) {
  //   await createContent(lessonID);
  // }
  await createContent("18");
  // 遍历清理 tempFiles中存储的文件
  for (const file of tempFiles) {
    if (fs.existsSync(file)) {
      fs.unlinkSync(file);
    }
  }
}

main();
